/*
 *  CEAnimationFactory.h
 *  LID Library
 *
 *  Created by Alexander Trutchenko on 09/11/11.
 *  Copyright 2011 League of Independent Developers. All rights reserved.
 *
 */

#include "cocos2dx_extentions/animations/CEAnimationFactory.h"

#include "cocos2d.h"


using namespace cocos2d;
using namespace lidlib;

namespace cocosext {

/////////////////////////////////////////////////////
//
/** CEAnimationFactory implementation
 */
/////////////////////////////////////////////////////

CEAnimationFactory* CEAnimationFactory::_instance = NULL;

static void framesWithTextureNames(CCMutableArray<CCSpriteFrame*>& frames, const std::list<std::string>& textureNames)
{
    for (std::list<std::string>::const_iterator it = textureNames.begin(); it !=  textureNames.end(); ++it)
	{
        CCSpriteFrame *spriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(it->c_str());
        if (spriteFrame == NULL)
        {
            CCTexture2D* texture = CCTextureCache::sharedTextureCache()->addImage(it->c_str());
            spriteFrame = CCSpriteFrame::frameWithTexture(texture, CCRect(CCPointZero.x, CCPointZero.y, texture->getContentSize().width, texture->getContentSize().height));
        }
		CCAssert(spriteFrame != NULL, NULL);
		frames.addObject(spriteFrame);
	}
} 

static void framesWithFormat(CCMutableArray<CCSpriteFrame*>& frames, const std::string& formatStr, unsigned int startIndex, unsigned int frameCount)
{	
	std::list<std::string> textureNames;
    char str[255];
	for (unsigned int i = startIndex; i < startIndex + frameCount; ++i)
	{		
        sprintf(str, formatStr.c_str(), i);
        textureNames.push_back(str);
	}
	
	framesWithTextureNames(frames, textureNames);
}

static void framesFromStringFormatDescription(CCMutableArray<CCSpriteFrame*>& frames, const LLDataNodeMap& description)
{	
    std::string formatString;
    getNodeMapValue(formatString, description, kCEAF_AnimationDescriptionKey_Texture);
    CCAssert(!formatString.empty(), NULL);
    
    unsigned int startIndex = 0; 
    getNodeMapValue(startIndex, description, kCEAF_AnimationDescriptionKey_StartIndex);
    
    unsigned int framesCount = 0;
    getNodeMapValue(framesCount, description, kCEAF_AnimationDescriptionKey_FramesCount);
    CCAssert(framesCount > 0, NULL);
    
	framesWithFormat(frames, formatString, startIndex, framesCount);
}

static void framesFromNormalFormatDescription(CCMutableArray<CCSpriteFrame*>& frames, const LLDataNodeMap& description)
{
	float frameWidth = 0.f;
    getNodeMapValue(frameWidth, description, kCEAF_AnimationDescriptionKey_FrameWidth);
	CCAssert(frameWidth > FLT_EPSILON, NULL);

	float frameHeight = 0.f;
    getNodeMapValue(frameHeight, description, kCEAF_AnimationDescriptionKey_FrameHeight);
	CCAssert(frameHeight > FLT_EPSILON, NULL);

    LLDataNodeList* sources = getNodeMapValuePointer<LLDataNodeList>(description, kCEAF_AnimationDescriptionKey_Sources);
    CCAssert(sources && !sources->empty(), NULL);
    for (LLDataNodeList::const_iterator it = sources->begin(); it != sources->end(); ++it)
	{
        const LLDataNodeMap& sourceDescription = *getNodeValuePointer<LLDataNodeMap>(*it);
        if (sourceDescription.empty())
            continue;

        float frameWidthLocal = 0.f;
        getNodeMapValue(frameWidthLocal, sourceDescription, kCEAF_AnimationDescriptionKey_FrameWidth);
        if(frameWidthLocal < FLT_EPSILON)
            frameWidthLocal = frameWidth;

        float frameHeightLocal = 0.f;
        getNodeMapValue(frameHeightLocal, sourceDescription, kCEAF_AnimationDescriptionKey_FrameHeight);
        if(frameHeightLocal < FLT_EPSILON)
            frameHeightLocal = frameHeight;

        float offsetX = 0.f;
        getNodeMapValue(offsetX, sourceDescription, kCEAF_AnimationDescriptionKey_OffsetX);

	    float offsetY = 0.f;
        getNodeMapValue(offsetY, sourceDescription, kCEAF_AnimationDescriptionKey_OffsetY);

        unsigned int framesCount = 0;
        getNodeMapValue(framesCount, sourceDescription, kCEAF_AnimationDescriptionKey_FramesCount);
        CCAssert(framesCount > 0, NULL);

        unsigned int framesInRow = 0;
        getNodeMapValue(framesInRow, sourceDescription, kCEAF_AnimationDescriptionKey_FramesInRow);
        if (framesInRow == 0)
			framesInRow = framesCount;

        unsigned int startIndex = 0;
        getNodeMapValue(startIndex, sourceDescription, kCEAF_AnimationDescriptionKey_StartIndex);

        std::string textureName;
        getNodeMapValue(textureName, sourceDescription, kCEAF_AnimationDescriptionKey_Texture);
        CCAssert(!textureName.empty(), NULL);
        CCTexture2D *texture = CCTextureCache::sharedTextureCache()->addImage(textureName.c_str());			
        for (unsigned int i = startIndex; i < framesCount + startIndex; ++i)
        {
	        frames.addObject(CCSpriteFrame::frameWithTexture(texture, CCRect(offsetX + frameWidthLocal * (i % framesInRow),
                                                                                   offsetY + frameHeightLocal * (i / framesInRow),
                                                                                   frameWidthLocal,       		  
                                                                                   frameHeightLocal)));	
        }
	}
}

//Return preloaded animation
CCAnimation* CEAnimationFactory::getAnimation(const std::string& animationName)
{
    return CCAnimationCache::sharedAnimationCache()->animationByName(animationName.c_str());
}
 
CCAnimation* CEAnimationFactory::getAnimation(const std::string& animationName, const LLDataNodeMap& description)
{
    CCAnimation* animation = getAnimation(animationName);
    if (animation != NULL)
        return animation;
    return loadAnimation(animationName, description);
}

CCAnimation* CEAnimationFactory::loadAnimation(const std::string& animationName, const LLDataNodeMap& description)
{
    CCAnimation* animation = getAnimation(animationName);
    if (animation != NULL)
        return animation;

    std::string descriptionFormat; 
    getNodeMapValue(descriptionFormat, description, kCEAF_AnimationDescriptionKey_Format);
    CCAssert(!descriptionFormat.empty(), NULL);

    CCMutableArray<CCSpriteFrame*> frames;

	if (descriptionFormat == kCEAF_AnimationDescriptionKey_Format_FormatString)
	{
		framesFromStringFormatDescription(frames, description);
	}
	else if (descriptionFormat == kCEAF_AnimationDescriptionKey_Format_Normal) 
	{
		framesFromNormalFormatDescription(frames, description);
	}
	else if (descriptionFormat == kCEAF_AnimationDescriptionKey_Format_Container)
	{
		LLDataNodeList* elements = getNodeMapValuePointer<LLDataNodeList>(description, kCEAF_AnimationDescriptionKey_Elements);
		CCAssert(elements && !elements->empty(), NULL);
		for (LLDataNodeList::const_iterator it = elements->begin(); it != elements->end(); ++it)
		{
			const LLDataNodeMap& elementDescription = *getNodeValuePointer<LLDataNodeMap>(*it);
			if (elementDescription.empty())
				continue;
			std::string elementDescriptionFormat; 
			getNodeMapValue(elementDescriptionFormat, elementDescription, kCEAF_AnimationDescriptionKey_Format);
			CCAssert(!elementDescriptionFormat.empty(), NULL);

//			CCMutableArray<CCSpriteFrame*> frames;

			if (elementDescriptionFormat == kCEAF_AnimationDescriptionKey_Format_FormatString)
			{
				framesFromStringFormatDescription(frames, elementDescription);
			}
			else if (elementDescriptionFormat == kCEAF_AnimationDescriptionKey_Format_Normal) 
			{
				framesFromNormalFormatDescription(frames, elementDescription);
			}
			else
				CCAssert(false && "CEAnimationFactory::loadAnimation: Unsopported animation description format", NULL);
		}
	}
	else
		CCAssert(false && "CEAnimationFactory::loadAnimation: Unsopported animation description format", NULL);

    //Set animation direction
    std::string direction; 
    getNodeMapValue(direction, description, kCEAF_AnimationDescriptionKey_Direction);
    if (direction.empty())
        direction = kCEAF_AnimationDescriptionKey_Direction_Forward;
    if (direction == kCEAF_AnimationDescriptionKey_Direction_Forward)
    {

    }
    else if (direction == kCEAF_AnimationDescriptionKey_Direction_ForwardBackward)
    {
        unsigned int nFramesCount = frames.count();
        for (unsigned int i = nFramesCount - 1; i > 0; --i)
        {
			CCSpriteFrame* fr = (CCSpriteFrame*)frames.getObjectAtIndex(i-1);
            frames.addObject(CCSpriteFrame::frameWithTexture(fr->getTexture(), fr->getRectInPixels(), fr->isRotated(), fr->getOffsetInPixels(), fr->getOriginalSizeInPixels()));
        }
    }
    else 
        CCAssert(false && "Unsopported direction", NULL);

    //Set animation period
    float frameDelay = 0.f;
    getNodeMapValue(frameDelay, description, kCEAF_AnimationDescriptionKey_Delay);
    CCAssert(frameDelay > FLT_EPSILON, NULL);

    //Create and add animation
    animation = CCAnimation::animationWithFrames(&frames, frameDelay);

    CCAnimationCache::sharedAnimationCache()->addAnimation(animation, animationName.c_str());
	_sceneAnimations[_currentScene].push_back(animationName);
	CEAnimationInfo::iterator it = _allAnimations.find(animationName);
	if (it == _allAnimations.end())
		_allAnimations.insert(CEAnimationInfo::value_type(animationName, 1));
	else
		it->second++;
	
	return animation;
}



void CEAnimationFactory::onAddScene(cocos2d::CCScene* scene)
{
	if (_sceneAnimations.find(scene) == _sceneAnimations.end()) //After replace
		_sceneAnimations.insert(CESceneAnimations::value_type(scene, std::list<std::string>()));
}

void CEAnimationFactory::onReplaceSceneWithTheSameResources(cocos2d::CCScene* oldScene, cocos2d::CCScene* newScene)
{
	if (oldScene == _currentScene)
		_currentScene = NULL;
	onRemoveScene(newScene);
	CESceneAnimations::iterator itOld = _sceneAnimations.find(oldScene);
	_sceneAnimations[newScene] = itOld->second;
	_sceneAnimations.erase(itOld);
}

void CEAnimationFactory::onRemoveScene(cocos2d::CCScene* scene)
{
	CESceneAnimations::iterator it = _sceneAnimations.find(scene);
	if (it == _sceneAnimations.end())
		return;
	if (scene == _currentScene)
		_currentScene = NULL;

	for (std::list<std::string>::const_iterator itAnimationName = it->second.begin(); itAnimationName != it->second.end(); ++itAnimationName)
	{
		CEAnimationInfo::iterator itInfo = _allAnimations.find(*itAnimationName);
		itInfo->second--;
		if (itInfo->second == 0)
		{
			CCAnimationCache::sharedAnimationCache()->removeAnimationByName(itAnimationName->c_str());
			_allAnimations.erase(itInfo);
		}
	}
	_sceneAnimations.erase(it);
}

void CEAnimationFactory::setCurrentScene(cocos2d::CCScene* scene)
{
	if (_sceneAnimations.find(scene) == _sceneAnimations.end())
		return;
	_currentScene = scene;
}

/////////////////////////////////////////////////////
//
/** Utility functions
*/
/////////////////////////////////////////////////////

void addSource(LLDataNodeMap* pNm, const char* animationFile, unsigned int framesCount, unsigned int framesInRow, float w/* = 0.f*/, float h/* = 0.f*/, float offset_x/* = 0.f*/, float offset_y/* = 0.f*/)
{
    setNodeMapValue<std::string>(animationFile, *pNm, kCEAF_AnimationDescriptionKey_Texture);
    setNodeMapValue<unsigned int>(framesCount, *pNm, kCEAF_AnimationDescriptionKey_FramesCount);
    setNodeMapValue<unsigned int>(framesInRow, *pNm, kCEAF_AnimationDescriptionKey_FramesInRow); 
    if (w > FLT_EPSILON)
        setNodeMapValue(w, *pNm, kCEAF_AnimationDescriptionKey_FrameWidth);
    if (h > FLT_EPSILON)
        setNodeMapValue(h, *pNm, kCEAF_AnimationDescriptionKey_FrameHeight);
    if (offset_x > FLT_EPSILON)
        setNodeMapValue(offset_x, *pNm, kCEAF_AnimationDescriptionKey_OffsetY);
    if (offset_y > FLT_EPSILON)
        setNodeMapValue(offset_y, *pNm, kCEAF_AnimationDescriptionKey_OffsetY);
}

void addBeginer(LLDataNodeMap& animation, float frameTime, float w, float h)
{
    setNodeMapValue(frameTime, animation, kCEAF_AnimationDescriptionKey_Delay);
    setNodeMapValue(w, animation, kCEAF_AnimationDescriptionKey_FrameWidth);
    setNodeMapValue(h, animation, kCEAF_AnimationDescriptionKey_FrameHeight);
}

void addAnimation(LLDataNodeMap& animation, 
                  const char* textureFormat, 
                  const char* direction,  
                  unsigned int startIndex, 
                  unsigned int framesCount, 
                  float framePeriod)
{
    setNodeMapValue<std::string>(textureFormat, animation, kCEAF_AnimationDescriptionKey_Texture);
    setNodeMapValue<std::string>(direction, animation, kCEAF_AnimationDescriptionKey_Direction);
    setNodeMapValue<unsigned int>(startIndex, animation, kCEAF_AnimationDescriptionKey_StartIndex);
    setNodeMapValue<unsigned int>(framesCount, animation, kCEAF_AnimationDescriptionKey_FramesCount);
    setNodeMapValue(framePeriod, animation, kCEAF_AnimationDescriptionKey_Delay);
}

void resetContainer(LLDataNodeMap& container, const char* direction, float framePeriod)
{
    setNodeMapValue<std::string>(kCEAF_AnimationDescriptionKey_Format_Container, container, kCEAF_AnimationDescriptionKey_Format);
    setNodeMapValue<std::string>(direction, container, kCEAF_AnimationDescriptionKey_Direction);
	setNodeMapValue(framePeriod, container, kCEAF_AnimationDescriptionKey_Delay);

	LLDataNodeList* elements = getNodeMapValuePointer<LLDataNodeList>(container, kCEAF_AnimationDescriptionKey_Elements);
	if (elements == NULL)
	{
		elements = new LLDataNodeList();
		setNodeMapValuePointer(elements, container, kCEAF_AnimationDescriptionKey_Elements);
	}
	else
		elements->clear();
}

void addElementToContainer(LLDataNodeMap& container, const LLDataNodeMap& element)
{
	LLDataNodeList* elements = getNodeMapValuePointer<LLDataNodeList>(container, kCEAF_AnimationDescriptionKey_Elements);
	CCAssert(elements != NULL, NULL);
    elements->push_back(LLDataNode(new LLDataNodeValue<LLDataNodeMap>(element)));
}

}; // end namespace cocosext

